/*
 * rpm.c - Generic RPM support
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
#define _GNU_SOURCE
#include <glib.h>
#include <libgen.h>
#include <libxml/xmlreader.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>

#include "cve-check-tool.h"
#include "plugin.h"
#include "rpm.h"
#include "rpm_common.c"
#include "util.h"

void rpm_extra_free(struct source_package_t *pkg)
{
        if (pkg && pkg->extra) {
                g_list_free_full(pkg->extra, g_free);
                pkg->extra = NULL;
        }
}

struct source_package_t *rpm_inspect_spec(const char *filename)
{
        printf("rpm_inspect_spec %s (this is the scan_package callback)\n",filename);
        struct source_package_t *t = NULL;
        autofree(GFile) *fi = g_file_new_for_path(filename);
        autofree(GError) *error = NULL;
        autofree(CveHashmap) *patches = NULL;
        if (!fi) {
                return NULL;
        }
        autofree(GFileInputStream) *fis = g_file_read(fi, NULL, &error);
        if (error) {
                g_printerr("Unable to read: %s\n", error->message);
                return NULL;
        }

        autofree(GDataInputStream) *dis = g_data_input_stream_new(G_INPUT_STREAM(fis));
        char *read = NULL;
        char *fpath = NULL;
        autofree(gchar) *name = NULL;
        autofree(gchar) *version = NULL;
        autofree(gchar) *release = NULL;
        autofree(CveHashmap) *macros = NULL;
        GList *lpatches = NULL;

        while ((read = g_data_input_stream_read_line(dis, NULL, NULL, NULL)) != NULL) {
                autofree(gstrv) *strv = NULL;
                const gchar *key = NULL;
                autofree(gchar) *value = NULL;

                read = g_strstrip(read);

                if (g_str_has_prefix(read, "%define") || g_str_has_prefix(read, "%global")) {
                        strv = g_strsplit(read, " ", 3);
                        if (g_strv_length(strv) != 3) {
                                goto clean;
                        }

                        gchar *mkey = g_strstrip(g_strdup_printf("%%{%s}", strv[1]));
                        gchar *mvalue = g_strstrip(g_strdup(strv[2]));

                        if (!macros) {
                                macros = cve_hashmap_new_full(string_hash, string_compare, g_free, g_free);
                        }

                        if (!cve_hashmap_contains(macros, mkey)) {
                                cve_hashmap_put(macros, mkey, mvalue);
                        } else {
                                g_free(mkey);
                                g_free(mvalue);
                        }
                        goto clean;
                } else if (g_str_has_prefix(read, "%patch")) {
                        autofree(gstrv) *splits = NULL;
                        autofree(gchar) *key = NULL;
                        gchar *val = NULL;
                        if (!patches) {
                                fprintf(stderr, "%s is broken. Applying patches without declaring them\n", filename);
                                continue;
                        }
                        strv = g_strsplit(read, " ", 1);
                        if (!strv) {
                                goto clean;
                        }

                        splits = g_strsplit(strv[0], "%patch", 2);
                        if (!splits) {
                                g_critical("Memory allocation failure");
                                goto clean;
                        }
                        if (g_strv_length(splits) == 1 || !splits[1] || g_str_equal(splits[1], "")) {
                                g_free(key);
                                key = g_strdup("0");
                        } else {
                                key = splits[1];
                        }
                        key = g_strchomp(firstword(key));
                        if (g_str_equal(key, "")) {
                                g_free(key);
                                key = g_strdup("0");
                        }
                        val = cve_hashmap_get(patches, key);
                        if (!val) {
                                fprintf(stderr,
                                        "%s is broken - applying \"patch%s\" which isn't declared\n",
                                        filename,
                                        key);
                                continue;
                        }
                        lpatches = g_list_append(lpatches, g_ascii_strdown(val, -1));
                        goto clean;
                }
                if (!strchr(read, ':')) {
                        goto clean;
                }

                strv = g_strsplit(read, ":", -1);
                if (g_strv_length(strv) < 2) {
                        goto clean;
                }
                key = g_strstrip(strv[0]);
                value = g_strjoinv(":", strv + 1);
                value = g_strstrip(value);

                if (str_iequal(key, "Name")) {
                        name = g_strdup(value);
                } else if (str_iequal(key, "Version")) {
                        version = g_strdup(value);
                } else if (str_iequal(key, "Release")) {
                        release = g_strdup(value);
                } else if (str_has_iprefix(key, "Patch")) {
                        autofree(gstrv) *splits = NULL;
                        autofree(gchar) *kkey = g_ascii_strdown(key, -1);
                        if (!patches) {
                                patches = cve_hashmap_new_full(string_hash, string_compare, g_free, g_free);
                                if (!patches) {
                                        g_critical("Memory allocation failure");
                                        goto clean;
                                }
                        }
                        splits = g_strsplit(kkey, "patch", 2);
                        if (!splits) {
                                g_critical("Memory allocation failure");
                                goto clean;
                        }
                        if (g_strv_length(splits) == 1 || !splits[1] || g_str_equal(splits[1], "")) {
                                cve_hashmap_put(patches, g_strdup("0"), g_strdup(value));

                        } else {
                                cve_hashmap_put(patches, g_strdup(splits[1]), g_strdup(value));
                        }

                        /* Store .nopatch in the pkg->extra */
                        if (str_has_isuffix(value, ".nopatch")) {
                                lpatches = g_list_append(lpatches, g_ascii_strdown(value, -1));
                        }
                }
        clean:
                g_free(read);
        }

        if (!name || !version || !release) {
                return NULL;
        }

        fpath = cve_get_file_parent(filename);
        if (!fpath) {
                return NULL;
        }

        if (macros) {
                name = demacro(macros, name);
                version = demacro(macros, version);
                release = demacro(macros, release);
        }

        t = calloc(1, sizeof(struct source_package_t));
        if (!t) {
                free(fpath);
                return NULL;
        }
        t->name = g_strdup(name);
        t->version = g_strdup(version);
        t->release = atoi(release);
        t->path = fpath;
        t->type = PACKAGE_TYPE_RPM;
        t->extra = lpatches;

        return t;
}

bool rpm_is_patched(struct source_package_t *pkg, char *id)
{
        /* Determine if its patched. */
        autofree(gchar) *pnamet = g_ascii_strdown((gchar *)id, -1);
        autofree(gchar) *pname = g_strdup_printf("%s.patch", pnamet);
        if (pkg->extra) {
                /* Patch validation */
                return g_list_find_custom(pkg->extra, pname, (GCompareFunc)g_utf8_collate) != NULL;
        }
        return false;
}

bool rpm_is_ignored(struct source_package_t *pkg, char *id)
{
        /* Determine if its ignored. */
        autofree(gchar) *pnamet = g_ascii_strdown((gchar *)id, -1);
        autofree(gchar) *pname = g_strdup_printf("%s.nopatch", pnamet);
        if (pkg->extra) {
                /* Patch validation */
                return g_list_find_custom(pkg->extra, pname, (GCompareFunc)g_utf8_collate) != NULL;
        }
        return false;
}

bool rpm_is_package(const char *filename)
{
        return g_str_has_suffix((const gchar *)filename, ".spec");
}

_module_export_ bool cve_plugin_module_init(CvePlugin *self)
{
        self->flags = PLUGIN_TYPE_PACKAGE;
        self->name = "rpm";
        self->is_ignored = rpm_is_ignored;
        self->is_patched = rpm_is_patched;
        self->is_package = rpm_is_package;
        self->scan_package = rpm_inspect_spec;
        self->free_package = rpm_extra_free;
        return true;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
